use std;
use std::any::{Any};
use std::rc::{Rc};
use tree;
use frag;
use geom;
use sdl2::pixels::{Color};
use signal;

////////////////////////////////////////////////////////////////////////////////////////////////////
// GUI virtual tree

/// Represents some GUI element to render onto the screen.
#[derive(Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct GUI(pub(crate) tree::Virt<DynComponent>);

impl From<frag::Frag> for GUI {
  fn from(frg: frag::Frag) -> Self {
    GUI(tree::Virt::frag(frg))
  }
}

/// Creates a GUI element equivalent to `frag::empty()`.
pub fn empty() -> GUI {
  frag::empty().into()
}

/// Creates a GUI element equivalent to `frag::fill(color)`.
pub fn fill(color: Color) -> GUI {
  frag::fill(color).into()
}

/// Creates a GUI element equivalent to `frag::rect(dim, color)`.
pub fn rect(dim: geom::Dim, color: Color) -> GUI {
  frag::rect(dim, color).into()
}

fn with_spine_info(spine: frag::FragSpine, info: frag::FragInfo, mut guis: Vec<GUI>) -> GUI {
  let virts: Vec<_> = unsafe {
    Vec::from_raw_parts(guis.as_mut_ptr() as *mut _, guis.len(), guis.capacity())
  };
  std::mem::forget(guis);
  GUI(tree::Virt::branch(spine, info, virts))
}

fn with_spine(spine: frag::FragSpine, guis: Vec<GUI>) -> GUI {
  with_spine_info(spine, frag::FragInfo::default(), guis)
}

/// Like `frag::pack_lr()` but combines the given GUI elements.
pub fn pack_lr(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackLR, guis) }

/// Like `frag::pack_rl()` but combines the given GUI elements.
pub fn pack_rl(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackRL, guis) }

/// Like `frag::pack_tb()` but combines the given GUI elements.
pub fn pack_tb(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackTB, guis) }

/// Like `frag::pack_bt()` but combines the given GUI elements.
pub fn pack_bt(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackBT, guis) }

/// Like `frag::overlay()` but combines the given GUI elements.
pub fn overlay(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::Overlay, guis) }

/// Like `frag::container()` but combines the given GUI elements.
pub fn container(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::Container, guis) }

impl GUI {

  pub fn container(self) -> GUI { container(vec![self]) }
  pub fn overlay(self, oth: GUI) -> GUI { overlay(vec![self, oth]) }
  pub fn underlay(self, oth: GUI) -> GUI { overlay(vec![oth, self]) }

  pub fn bounds(mut self, bounds: geom::Dim) -> GUI {
    self.0.info_mut().map(|i| i.bounds = bounds);
    self
  }

  pub fn width(mut self, width: geom::Len) -> GUI {
    self.0.info_mut().map(|i| i.bounds.0 = width);
    self
  }

  pub fn height(mut self, height: geom::Len) -> GUI {
    self.0.info_mut().map(|i| i.bounds.1 = height);
    self
  }

  pub fn align(mut self, align_h: frag::AlignH, align_v: frag::AlignV) -> GUI {
    self.0.info_mut().map(|i| { i.align_h = align_h; i.align_v = align_v; });
    self
  }

  pub fn align_h(mut self, align_h: frag::AlignH) -> GUI {
    self.0.info_mut().map(|i| i.align_h = align_h);
    self
  }

  pub fn align_v(mut self, align_v: frag::AlignV) -> GUI {
    self.0.info_mut().map(|i| i.align_v = align_v);
    self
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// DynComponent: Type-erased `Component`

/// A dynamically-typed component. This is used as the "Abstract" node type in a GUI tree.
#[derive(Clone)]
#[repr(transparent)]
pub struct DynComponent(Rc<dyn ComponentAny>);

/// Internal trait used to erase the type information from the `Component` trait, making it
/// object-safe.
pub trait ComponentAny {
  fn as_any(&self) -> &dyn Any;
  fn init(&self) -> Box<dyn Any>;
  fn render(&self, ctx: &GUIContext, s: &dyn Any) -> GUI;
  fn compatible(&self, cfg: &dyn Any) -> bool;
  fn eq(&self, oth: &dyn Any) -> bool;
  fn should_update(&self, s: &dyn Any, next: &dyn Any, next_s: &dyn Any) -> bool;
}

/// Create dynamic component
pub fn dynamic<C: ComponentAny + 'static>(c: C) -> GUI {
  GUI(tree::Virt::abs_props(DynComponent(Rc::new(c))))
}

// tree::Abstract impl. This is mostly just boilerplate, calling ComponentAny trait functions from a
// DynComponent.

impl tree::Abstract for DynComponent {
  type State = Box<dyn Any>;
  type Context = GUIContext;

  fn initial_state(&self, _: &mut GUIContext) -> Box<dyn Any> {
    self.0.init()
  }
  fn render(&self, ctx: &mut GUIContext, s: &Box<dyn Any>) -> tree::Virt<DynComponent> {
    self.0.render(ctx, s.as_ref()).0
  }
  fn compatible(&self, oth: &Self) -> bool {
    self.0.compatible(oth.0.as_any())
  }
  fn should_update(&self, s: &Box<dyn Any>, next: &Self, next_s: &Box<dyn Any>) -> bool {
    self.0.should_update(s.as_ref(), next.0.as_any(), next_s.as_ref())
  }
}

// Dynamically typed Eq instance

impl PartialEq for DynComponent {
  fn eq(&self, rhs: &Self) -> bool {
    self.0.eq(rhs.0.as_any())
  }
}

impl Eq for DynComponent {}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Context

/// Internal context used by ComponentAny, for direct control over contextual information.
pub struct GUIContext {
  pub path: tree::PathBuf,
  pub evt_disp: signal::EvtDispatcherPtr,
}

impl GUIContext {
  pub fn with_dispatcher(evt_disp: signal::EvtDispatcherPtr) -> Self {
    GUIContext { path: vec![], evt_disp }
  }
}

impl tree::NodeContext for GUIContext {
  fn push_path(&mut self, el: tree::PathElem) {
    self.path.push(el);
  }
  fn pop_path(&mut self) {
    self.path.pop();
  }
}
